/*
 * @Date         : 2024-05-17 14:38:38 星期5
 * @Author       : xut
 * @Description  :
 */
import { randomBytes, createHash } from "node:crypto"
import bcrypt from "bcryptjs"
import * as grpc from "@grpc/grpc-js"
import {
  createUser,
  findUniqueUser,
  findUser,
  signTokens,
  updateUser,
} from "../services/user.service"
import { SignUpUserInput__Output } from "../../pb/auth/SignUpUserInput"
import { SignInUserInput__Output } from "../../pb/auth/SignInUserInput"
import { SignInUserResponse } from "../../pb/auth/SignInUserResponse"
import { RefreshTokenInput__Output } from "../../pb/auth/RefreshTokenInput"
import { RefreshTokenResponse } from "../../pb/auth/RefreshTokenResponse"
import { GenericResponse } from "../../pb/auth/GenericResponse"
import { signJwt, verifyJwt } from "../utils/jwt"
import { envConfig } from "../config/default"
import { redisClient } from "../utils/connectRedis"
import { Email } from "../utils/email"
import { VerifyEmailInput__Output } from "../../pb/auth/VerifyEmailInput"

export async function registerHandler(
  req: grpc.ServerUnaryCall<SignUpUserInput__Output, GenericResponse>,
  res: grpc.sendUnaryData<GenericResponse>
) {
  try {
    const hashedPassword = await bcrypt.hash(req.request.password, 12)
    const user = await createUser({
      email: req.request.email.toLowerCase(),
      name: req.request.name,
      password: hashedPassword,
      photo: req.request.photo,
      provider: "local",
    })

    const verification_code = randomBytes(20).toString("hex")
    const hashed_verification_code = createHash("sha256")
      .update(verification_code)
      .digest("hex")

    await updateUser(
      { id: user.id },
      { verification_code: hashed_verification_code }
    )

    const redirectUrl = `https://localhost:9000/api/verifyemail?code=${verification_code}`

    try {
      const emailer = new Email(user, redirectUrl)
      await emailer.sendVerificationCode()

      res(null, { status: "success", message: "Email verification code sent" })
    } catch (error: any) {
      await updateUser({ id: user.id }, { verification_code: null })
      res({
        code: grpc.status.INTERNAL,
        message: error.message,
      })
    }

    // return res(null, {
    //   user: {
    //     id: user.id,
    //     name: user.name,
    //     email: user.email,
    //     photo: user.photo!,
    //     provider: user.provider!,
    //     role: user.role!,
    //     created_at: { seconds: user.created_at.getTime() / 1000 },
    //     updated_at: { seconds: user.updated_at.getTime() / 1000 },
    //   },
    // })
  } catch (error: any) {
    if (error.code === "P2002") {
      return res({
        code: grpc.status.ALREADY_EXISTS,
        message: "Email already exists",
      })
    }

    return res({ code: grpc.status.INTERNAL, message: error.message })
  }
}

export async function loginHandler(
  req: grpc.ServerUnaryCall<SignInUserInput__Output, SignInUserResponse>,
  res: grpc.sendUnaryData<SignInUserResponse>
) {
  try {
    const user = await findUser({ email: req.request.email })

    if (!user) {
      return res({
        code: grpc.status.INVALID_ARGUMENT,
        message: "Invalid email or password",
      })
    }

    if (!user.verified) {
      return res({
        code: grpc.status.INVALID_ARGUMENT,
        message: "Account not verified",
      })
    }

    const isValid = await bcrypt.compare(req.request.password, user.password)

    if (!isValid) {
      return res({
        code: grpc.status.INVALID_ARGUMENT,
        message: "Invalid email or password",
      })
    }

    const { access_token, refresh_token } = await signTokens(user)

    res(null, {
      status: "success",
      access_token,
      refresh_token,
    })
  } catch (error: any) {
    return res({
      code: grpc.status.INTERNAL,
      message: error.message,
    })
  }
}

export async function refreshAccessTokenHandler(
  req: grpc.ServerUnaryCall<RefreshTokenInput__Output, RefreshTokenResponse>,
  res: grpc.sendUnaryData<RefreshTokenResponse>
) {
  try {
    const refreshToken = req.request.refresh_token
    const message = "Could not refresh access token"

    if (!refreshToken)
      return res({ code: grpc.status.PERMISSION_DENIED, message })

    const decoded = await verifyJwt<{ sub: string }>(
      refreshToken,
      "refreshTokenPublicKey"
    )

    if (!decoded) return res({ code: grpc.status.PERMISSION_DENIED, message })

    const session = await redisClient.get(decoded.sub)

    if (!session) return res({ code: grpc.status.PERMISSION_DENIED, message })

    const user = await findUniqueUser({ id: JSON.parse(session).id })

    if (!user) return res({ code: grpc.status.PERMISSION_DENIED, message })

    const accessToken = await signJwt(
      { sub: user.id },
      "accessTokenPrivateKey",
      {
        expiresIn: `${envConfig.accessTokenExpiresIn}m`,
      }
    )

    return res(null, { access_token: accessToken, refresh_token: refreshToken })
  } catch (error: any) {
    return res({
      code: grpc.status.INTERNAL,
      message: error.message,
    })
  }
}

export async function verifyEmailHandler(
  req: grpc.ServerUnaryCall<VerifyEmailInput__Output, GenericResponse>,
  res: grpc.sendUnaryData<GenericResponse>
) {
  try {
    const reqVerificationCode = req.request.verification_code

    const verification_code = createHash("sha256")
      .update(reqVerificationCode)
      .digest("hex")

    const user = await findUniqueUser({ verification_code })

    if (!user) {
      return res({
        code: grpc.status.UNAUTHENTICATED,
        message: "Could not verify email",
      })
    }

    await updateUser(
      { id: user.id },
      { verified: true, verification_code: null }
    )

    return res(null, {
      status: "success",
      message: "Email verified successfully",
    })
  } catch (error: any) {
    return res({
      code: grpc.status.INTERNAL,
      message: error.message,
    })
  }
}
